﻿using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Samurai.Wakizashi.Workflow;

namespace Samurai.Workflow.Tests
{
    [TestClass]
    public class Sequential_Workflow
    {
        private bool _workflowCompletedEventRaised;
        private bool _workflowFailedEventRaised;

        [TestMethod]
        public void Executes_queued_steps_sequentially()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext {Value1 = "", Value2 = 0};
            
            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new TestWorkflowStep2();
            workflow.RegisterStep(step1);
            workflow.RegisterStep(step2);

            //Kickoff workflow
            workflow.Start();

            //Verify context was touched by each step
            Assert.IsTrue(workflow.Context.Value1 == "123" && workflow.Context.Value2 == 123);
        }

        [TestMethod]
        public void Evaluates_execution_predicates_before_executing_steps()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new TestWorkflowStep2();
            workflow.RegisterStep(step1, c => true);
            workflow.RegisterStep(step2, c => false);

            //Kickoff workflow
            workflow.Start();

            //Verify context was touched by each step
            Assert.IsTrue(workflow.Context.Value1 == "123" && workflow.Context.Value2 == 0);
        }

        [TestMethod]
        public void Raises_event_when_all_steps_have_completed()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new TestWorkflowStep2();
            workflow.RegisterStep(step1);
            workflow.RegisterStep(step2);

            //Attach to workflow completed event
            workflow.WorkflowCompleted += Workflow_WorkflowCompleted;

            //Kickoff workflow
            workflow.Start();

            //Verify completed event was raised
            Assert.IsTrue(_workflowCompletedEventRaised);
        }

        [TestMethod]
        public void Raises_event_when_a_critical_step_fails()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new FailedWorkflowStep { IsCritical = true };
            var step3 = new TestWorkflowStep2();
            workflow.RegisterStep(step1);
            workflow.RegisterStep(step2);
            workflow.RegisterStep(step3);

            //Attach to workflow failed event
            workflow.WorkflowFailed += Workflow_Failed;

            //Kickoff workflow
            workflow.Start();

            //Verify completed event was raised
            Assert.IsTrue(_workflowFailedEventRaised);
        }

        [TestMethod]
        public void Halts_execution_when_a_critical_step_fails()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new FailedWorkflowStep {IsCritical = true};
            var step3 = new TestWorkflowStep2();
            workflow.RegisterStep(step1);
            workflow.RegisterStep(step2);
            workflow.RegisterStep(step3);

            //Attach to workflow failed event
            workflow.WorkflowFailed += Workflow_Failed;

            //Kickoff workflow
            workflow.Start();

            //Verify completed event was raised
            Assert.IsTrue(testContext.Value1 == "123" && testContext.Value2 == 0);
        }

        [TestMethod]
        public void Continues_execution_when_a_noncritical_step_fails()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Create steps and register them in the order you want them to execute
            var step1 = new TestWorkflowStep1();
            var step2 = new FailedWorkflowStep { IsCritical = false };
            var step3 = new TestWorkflowStep2();
            workflow.RegisterStep(step1);
            workflow.RegisterStep(step2);
            workflow.RegisterStep(step3);

            //Attach to workflow failed event
            workflow.WorkflowFailed += Workflow_Failed;

            //Kickoff workflow
            workflow.Start();

            //Verify completed event was raised
            Assert.IsTrue(testContext.Value1 == "123" && testContext.Value2 == 123);
        }

        [TestMethod]
        public void Executes_delegate_actions_as_workflow_steps()
        {
            //Setup workflow context
            var testContext = new TestWorkflowContext { Value1 = "", Value2 = 0 };

            //Create workflow and provide context
            var workflow = new SequentialWorkflow<TestWorkflowContext>(testContext);

            //Register delegate actions in the order you want them to execute
            workflow.RegisterStep(Step1);
            workflow.RegisterStep(Step2);

            //Kickoff workflow
            workflow.Start();

            //Verify completed event was raised
            Assert.IsTrue(testContext.Value1 == "123" && testContext.Value2 == 123);
        }

        [TestMethod]
        public void Reports_delegated_step_name_upon_failure()
        {
            var wf = new SequentialWorkflow<TestWorkflowContext>(new TestWorkflowContext());
            wf.RegisterStep(() => { throw new Exception("Test Exception from delegated workflow step."); }, 
                ThreadType.Current, null, "TestName");

            string reportedMessage = null;
            wf.OnFailed(args =>
                            {
                                reportedMessage = args.FailedWorkflowStep.StepName;
                            });
            wf.Start();

            Assert.IsFalse(string.IsNullOrEmpty(reportedMessage));
            Assert.IsTrue(reportedMessage.Contains("TestName"));
        }

        private void Step1(TestWorkflowContext context)
        {
            context.Value1 = "123";
        }

        private void Step2(TestWorkflowContext context)
        {
            context.Value2 = 123;
        }

        private void Workflow_WorkflowCompleted(object sender, WorkflowCompletedEventArgs<TestWorkflowContext> e)
        {
            _workflowCompletedEventRaised = true;
        }

        private void Workflow_Failed(object sender, WorkflowCompletedEventArgs<TestWorkflowContext> e)
        {
            _workflowFailedEventRaised = true;
        }
    }

    internal class TestWorkflowStep1 : WorkflowStepBase<TestWorkflowContext>
    {
        #region Overrides of WorkflowStepBase<TestWorkflowContext>

        /// <summary>
        /// Executes the step.
        /// </summary>
        public override void ExecuteStep()
        {
            Context.Value1 = "123";

            NotifyStepCompleted();
        }

        #endregion
    }

    internal class TestWorkflowStep2 : WorkflowStepBase<TestWorkflowContext>
    {
        #region Overrides of WorkflowStepBase<TestWorkflowContext>

        /// <summary>
        /// Executes the step.
        /// </summary>
        public override void ExecuteStep()
        {
            Context.Value2 = 123;

            NotifyStepCompleted();
        }

        #endregion
    }

    internal class FailedWorkflowStep : WorkflowStepBase<TestWorkflowContext>
    {
        #region Overrides of WorkflowStepBase<TestWorkflowContext>

        /// <summary>
        /// Executes the step.
        /// </summary>
        public override void ExecuteStep()
        {
            try
            {
                throw new InvalidOperationException("Test exception");
            }
            catch (Exception ex)
            {
                NotifyStepFailed(ex);
            }
        }

        #endregion
    }
}
